#include "ScanGroup.h"
#include "KernelHandler.h"
#include <iostream>

namespace GPUCC
{
	ScanGroup::ScanGroup(unsigned int Size, unsigned int MaxItemSize, KernelHandler& Handler, cl::Program* prog, cl::Context* context, cl::CommandQueue* queue) : BaseScanGroup(Size, MaxItemSize, Handler, prog, context, queue)
	{
		cl_int Result;

		this->m_DeviceData = cl::Buffer(*this->m_Context, CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR, // for some reason CL_MEM_HOST_WRITE_ONLY return CL_INVALID_VALUE error!
		this->m_Data.size(),
		this->m_Data.data(),
		&Result);

		if (Result != CL_SUCCESS)
		{
			std::cout << "error alloc data buffer" << std::endl;
		}
		else
		{
			this->m_DeviceInfo = cl::Buffer(*this->m_Context, CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR, // for some reason CL_MEM_HOST_WRITE_ONLY return CL_INVALID_VALUE error!
			this->m_Info.size() * sizeof(BUFFER_INFO),
			this->m_Info.data(),
			&Result);

			if (Result != CL_SUCCESS)
			{
				std::cout << "error alloc info buffer" << std::endl;
			}
			else
			{
				this->m_DeviceHash = cl::Buffer(*this->m_Context, CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR, // for some reason CL_MEM_HOST_WRITE_ONLY return CL_INVALID_VALUE error!
				this->m_Hashes.size() * sizeof(MD5Signature::MD5Array),
				this->m_Hashes.data(),
				&Result);

				if (Result != CL_SUCCESS)
				{
					std::cout << "error alloc hashes buffer" << std::endl;
				}
			}
		}
	}

	ScanGroup::~ScanGroup()
	{
	}

	void ScanGroup::Scan()
	{
		cl_int Result;

		this->m_Finished.Reset();

		cl::Event DataWriteFinishEvent;

		Result = this->m_Queue->enqueueWriteBuffer(this->m_DeviceData, CL_FALSE, 0, this->m_CurrentOffset, this->m_Data.data(), NULL, &DataWriteFinishEvent);
		
		if (Result != CL_SUCCESS)
		{
			std::cout << "error writing buffer" << std::endl;
		}
		else
		{
			cl::Event InfoWriteFinishEvent;
			Result = this->m_Queue->enqueueWriteBuffer(this->m_DeviceInfo, CL_FALSE, 0, this->m_Names.size() * sizeof(BUFFER_INFO), this->m_Info.data(), NULL, &InfoWriteFinishEvent);

			if (Result != CL_SUCCESS)
			{
				std::cout << "error in setting up writing event callback" << std::endl;
			}
			else
			{
				std::vector<cl::Event> WriteEvents;
				WriteEvents.push_back(DataWriteFinishEvent);
				WriteEvents.push_back(InfoWriteFinishEvent);
				this->m_Queue->flush();

				cl::Kernel localKernel(*this->m_Program, "calc_md5_items", &Result);

				if (Result != CL_SUCCESS)
				{
					std::cout << "error in kernel ctor" << std::endl;
				}
				else
				{
					cl::Event KernelFinishEvent;

					localKernel.setArg(0, this->m_DeviceData);
					localKernel.setArg(1, this->m_DeviceInfo);
					localKernel.setArg(2, this->m_DeviceHash);
					Result = this->m_Queue->enqueueNDRangeKernel(localKernel, cl::NullRange, cl::NDRange(this->m_Names.size()), cl::NullRange, &WriteEvents, &KernelFinishEvent);
					this->m_Queue->flush();
				
					WriteEvents.clear();

					if (Result != CL_SUCCESS)
					{
						std::cout << "error in kernel queue" << std::endl;
					}
					else
					{
						if (Result != CL_SUCCESS)
						{
							std::cout << "error in setting up kernel event callback" << std::endl;
						}
						else
						{
							std::vector<cl::Event> KernelEvents;
							KernelEvents.push_back(KernelFinishEvent);

							cl::Event CopyResultFinishEvent;

							Result = this->m_Queue->enqueueReadBuffer(this->m_DeviceHash, CL_FALSE, 0, this->m_Names.size() * sizeof(MD5Signature::MD5Array), this->m_Hashes.data(), &KernelEvents, &CopyResultFinishEvent);
							this->m_Queue->flush();

							KernelEvents.clear();

							if (Result != CL_SUCCESS)
							{
								std::cout << "error reading buffer" << std::endl;
							}
							else
							{
								Result = CopyResultFinishEvent.setCallback(CL_COMPLETE, ScanGroup::hash_read, this);

								if (Result != CL_SUCCESS)
								{
									std::cout << "error in setting up read event callback" << std::endl;
								}
							}
						}
					}
				}
			}
		}

		if (Result != CL_SUCCESS)
		{
			this->SetScanFinished();
		}
	}

	void ScanGroup::AddItemInfo(unsigned int Size)
	{
	}

	void CL_CALLBACK ScanGroup::hash_read(cl_event event, cl_int command_exec_status, void* user_data)
	{
		ScanGroup* Instance = (ScanGroup*)user_data;
	
		if (command_exec_status != CL_COMPLETE)
		{
			std::cout << "error in return hash values, code -" << command_exec_status << std::endl;
			Instance->SetScanFinished();
		}
		else
		{
			Instance->m_Handler.OnKernelOutputRead(Instance);
		}
	}
}